#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unordered_map>
#include "protocol.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    using func_t=std::function<bool(const HttpRequest&,HttpResponse&)>;//传入请求，构建响应

    class HttpServer
    {
    public:
        HttpServer(func_t func,const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_func(func)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建监听socket套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                exit(SOCKET_ERR);
            }
            //1.2设置地址复用
            int opt=1;
            setsockopt(_listenSocket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                exit(BIND_ERR);
            }
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                exit(LISTEN_ERR);
            }
        }
        void Start()//启动服务器
        {
            while(1)
            {
                //4、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数，拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    continue;
                }           
                //6、使用accept的返回值sock进行通信，均为文件操作
                pid_t id=fork();
                if(id==0)//子进程
                {
                    close(_listenSocket);//关闭子进程的监听套接字，使监听套接字计数-1（防止下一步孙子进程拷贝）
                    if(fork()>0) exit(0);//让子进程退出，孙子进程成为孤儿进程，交给1号进程托管回收其退出资源
                    HandlerHttp(sock);
                    close(sock);//必须关闭使用完毕的sock，否则文件描述符泄漏（虽然下一句代码exit(0),孙子进程退出也会释放文件描述符，最好还是手动关一下）
                    exit(0);
                }
                close(sock);//这是用于通信的套接字fd，父进程和孙子进程都有这个文件描述符，父进程关了，该文件描述符引用技术-1，直至孙子进程退出，该fd才会减为0，关闭
                //父进程
                pid_t ret=waitpid(id,nullptr,0);//这里不能用非阻塞等待，否则父进程先跑去执行其他代码，可能会被卡在accept出不来了（没有新的客户端来连接的话）
            }
        }
        // void registerCb(std::string serviceName,func_t cb)//注册方法
        // {
        //     funcs.insert(std::make_pair(serviceName,cb));
        // }
        void HandlerHttp(int sock)
        {
            //1、读取完整的http请求
            char buffer[4096];
            HttpRequest req;
            HttpResponse resp;
            size_t n=recv(sock,buffer,sizeof(buffer)-1,0);//读取套接字中的内容
            if(n>0)
            {
                buffer[n]=0;//添加'\0'
                req._inBuffer=buffer;//获得序列化请求
                //2、反序列化
                //从客户端请求中解析获得请求行(请求方法、请求url、http版本)、请求资源的后缀、请求资源的大小
                req.Parse();//对请求进行解析
                //3、回调_func方法，由http请求获得http响应  _func(req,resp)
                //4、对http响应resp进行序列化
                _func(req,resp);
                //funcs[req._path](req,resp);
                send(sock,resp._outBuffer.c_str(),resp._outBuffer.size(),0);//5、send发送
            }
        }
        ~HttpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求，不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
        func_t _func;//回调函数，传入请求，构建响应
        //std::unordered_map<std::string,func_t> funcs;//路径对应的回调函数的哈希
    };
}



std::string suffdeixDesc(const std::string& suffix)//将后缀转换为对应的响应报头
{
    std::string ret="Content-Type: ";
    if(suffix==".html"){ret+="text/html\r\n";}
    else if(suffix==".jpg"){ret+="image/jpeg\r\n";}
    return ret;
}
bool Get(const HttpRequest& req,HttpResponse& resp)
{
    // if(req._path=="test.py")
    // {
    //     //建立进程间通信，pipe
    //     //fork创建子进程，execl("/bin/python",test.py)进行进程程序替换
    //     //父进程，将req._parm通过管道写给某些后端语言，例如py、java、php等
    //     //....
    //     return true;
    // }
    // if(req._path=="/search")
    // {
    //     //如果PATH是"/search"，可以在这里写具体的C++方法，提供相应的服务
    //     //....
    //     return true;
    // }
    //打印客户端的请求的一些信息
    std::cout<<"-----------httpStart------------"<<std::endl;
    std::cout<<req._inBuffer<<std::endl;
    std::cout<<"method:"<<req._method<<std::endl;
    std::cout<<"url:"<<req._url<<std::endl;
    std::cout<<"httpVersion:"<<req._httpVersion<<std::endl;
    std::cout<<"path:"<<req._path<<std::endl;
    std::cout<<"suffix:"<<req._suffix<<std::endl;
    std::cout<<"size:"<<req._size<<"字节"<<std::endl;
    std::cout<<"-----------httpEnd--------------"<<std::endl;
    //服务器的状态行、响应报头、空行、响应正文等信息
    //状态行
    std::string respLine="HTTP/1.1 200 OK\r\n";
    //std::string respLine="HTTP/1.1 307 Temporary Redirect\r\n";//重定向，配合"Location: "使用
    //响应报头
    std::string respHeader=suffdeixDesc(req._suffix);//将后缀转换为对应的响应报头
    if(req._size>0)
    {
        respHeader+="Content-Length: ";
        respHeader+=std::to_string(req._size+1);//为了和后面的body.size()大小匹配
        respHeader+="\r\n";
    }
    //respHeader+="Location: https://www.csdn.net/\r\n";
    //写入Cookie
    respHeader+="Set-Cookie: name=12345abcde;Max-Age=120\r\n";//设置Cookie响应报头
    //往后，每次http请求都会自动携带曾经设置的所有Cookie，帮助服务器的鉴权行为
    //空行
    std::string respBlack="\r\n";
    std::string body;
    body.resize(req._size+1);
    if(!Util::readFile(req._path,(char*)body.c_str(),req._size))//将_path路径下的文件内容读到buffer里
    {
        Util::readFile(html_404,(char*)body.c_str(),req._size);//访问的资源不存在，body的路径就是404html的路径
        respLine="HTTP/1.1 404 Not Found\r\n";//同时，状态行修改为404
    }
    std::cout<<"-----------httpStart------------"<<std::endl;
    std::cout<<(respLine+respHeader+respBlack)<<std::endl;
    std::cout<<"-----------httpEnd--------------"<<std::endl;
    resp._outBuffer=respLine+respHeader+respBlack+body;
    return true;
}